
#pragma once
#ifndef PROGRAM_HPP
#define PROGRAM_HPP

#include <list>
#include <memory>
#include <string>
#include <unordered_map>

#include "../Math.hpp"
#include "../Model/VertexStructure.hpp"
#include "ProgramSpecify.hpp"

namespace Program {

class ProgramState;

using ProgramRef = std::shared_ptr<ProgramState>;

class ProgramState {
private:
  ProgramState() : _id(0), _bind_loc(false) {}
  void Remove();

public:
  ~ProgramState();
  GLuint GetID() const { return _id; }
  bool IsBindAttrib() const { return _bind_loc; }

  static ProgramRef CreatProgram();
  static void RemoveProgram(const ProgramRef &program);

  static void RemoveAll();

  enum BuiltInUniform {
    UNIFORM_PERSPECTIVE_MAT,
    UNIFORM_MODELVIEW_MAT,
    UNIFORM_MVP_MAT,
    UNIFROM_NORMAL_MAT,
    UNIFORM_TIME_VEC4,
    UNIFORM_SINTIME_VEC4,
    UNIFORM_COSTIME_VEC4,
    COUNT
  };

  static std::string GetBuiltInUniformName(BuiltInUniform type);
  /*
    - Create a \p type shader object
    - Compile this shader
    - Attach this shader to the program
  */
  void CreateShaderWithFileName(const std::string &fileName, ShaderType type);
  void CreateShaderWithFileNameExt(const std::string &fileName);
  void CreateShaderWithByteArrays(const GLchar *shaderSrc, ShaderType type);

  // TODO:Add Shader Class Surport
  void InitProgram(/* Attach Compiled shader and link program */);

  // whether clear compiled shader object
  void LinkProgram(bool clear = true);

  void UseProgram();

  void UseProgramStages(GLuint pipeline, ShaderStage stage);

  void UnUse();

  void BindAttribLocation(GLuint index, const std::string &name);

  void BindAttributes(const std::vector<Model::VertexAttribFormat> &formats);

  void BindAllAttributes();

  void BindFragAttribLocation(GLuint index, const std::string &name) const;

  void GetAttributeLocation(const std::string &name) const;

  void UpdateBuiltInUniform(const glm::mat4 &mv);

  GLuint GetUniformLocation(const std::string &name);

  void SetUniformFloat(const std::string &name, GLfloat val);

  void SetUniformFloatArray(const std::string &name, GLfloat *val, GLsizei count);

  void SetUniformDouble(const std::string &name, GLdouble val);

  void SetUniformDoubleArray(const std::string &name, GLdouble *val, GLsizei count);

  void SetUniformFloat2(const std::string &name, glm::vec2 val);

  void SetUniformFloat2Array(const std::string &name, glm::vec2 *val, GLsizei count);

  void SetUniformDouble2(const std::string &name, glm::dvec2 val);

  void SetUniformDouble2Array(const std::string &name, glm::dvec2 *val, GLsizei count);

  void SetUniformDouble3Array(const std::string &name, glm::dvec3 *val, GLsizei count);

  void SetUniformDouble3(const std::string &name, glm::dvec3 val);

  void SetUniformFloat4(const std::string &name, glm::vec4 val);

  void SetUniformFloat4Array(const std::string &name, glm::vec4 *val, GLsizei count);

  void SetUniformInt(const std::string &name, GLint val);
  void SetUniformIntArray(const std::string &name, GLint *val, GLsizei count);
  void SetUniformInt2(const std::string &name, glm::ivec2 val);
  void SetUniformInt2Array(const std::string &name, glm::ivec2 *val, GLsizei count);

  void SetUniformInt3(const std::string &name, glm::ivec3 val);
  void SetUniformInt3Array(const std::string &name, glm::ivec3 *val, GLsizei count);
  void SetUniformInt4(const std::string &name, glm::ivec4 val);
  void SetUniformInt4Array(const std::string &name, glm::ivec4 *val, GLsizei count);

  // unsigned int uniform
  void SetUniformUInt(const std::string &name, GLuint val);
  void SetUniformUIntArray(const std::string &name, GLuint *val, GLsizei count);
  void SetUniformUInt2(const std::string &name, glm::uvec2 val);
  void SetUniformUInt2Array(const std::string &name, glm::uvec2 *val, GLsizei count);
  void SetUniformUInt3(const std::string &name, glm::uvec3 val);
  void SetUniformUInt3Array(const std::string &name, glm::uvec3 *val, GLsizei count);
  void SetUniformUInt4(const std::string &name, glm::uvec4 val);
  void SetUniformUInt4Array(const std::string &name, glm::uvec4 *val, GLsizei count);

  void SetUniformMat2fArray(const std::string &name, const glm::mat2 *mat, GLsizei count);
  void SetUniformMat2dArray(const std::string &name, const glm::dmat2 *mat,
                            GLsizei count);
  void SetUniformMat3fArray(const std::string &name, const glm::mat3 *mat, GLsizei count);
  void SetUniformMat2dArray(const std::string &name, glm::dmat3 *mat, GLsizei count);
  void SetUniformMat4fArray(const std::string &name, const glm::mat4 *mat, GLsizei count);
  void SetUniformMat4dArray(const std::string &name, glm::dmat4 *mat, GLsizei count);

  void SetUniformMat2x3fArray(const std::string &name, const glm::mat2x3 *mat,
                              GLsizei count);
  void SetUniformMat2x3dArray(const std::string &name, const glm::dmat2x3 *mat,
                              GLsizei count);
  void SetUniformMat2x4fArray(const std::string &name, const glm::mat2x4 *mat,
                              GLsizei count);
  void SetUniformMat2x4dArray(const std::string &name, const glm::dmat2x4 *mat,
                              GLsizei count);
  void SetUniformMat3x4fArray(const std::string &name, const glm::mat3x4 *mat,
                              GLsizei count);
  void SetUniformMat3x4dArray(const std::string &name, const glm::dmat3x4 *mat,
                              GLsizei count);
  void SetUniformMat3x2fArray(const std::string &name, const glm::mat3x2 *mat,
                              GLsizei count);
  void SetUniformMat3x2dArray(const std::string &name, const glm::dmat3x2 *mat,
                              GLsizei count);
  void SetUniformMat4x2fArray(const std::string &name, const glm::mat4x2 *mat,
                              GLsizei count);
  void SetUniformMat4x2dArray(const std::string &name, const glm::dmat4x2 *mat,
                              GLsizei count);
  void SetUniformMat4x3fArray(const std::string &name, const glm::mat4x3 *mat,
                              GLsizei count);
  void SetUniformMat4x3dArray(const std::string &name, const glm::dmat4x3 *mat,
                              GLsizei count);

protected:
  void CompileShaderFromFile(GLuint id, const std::string &fileName);
  void CompileShaderFromSrc(GLuint id, const std::string &shaderSrc);
  void CompileShaderFormBin(GLuint id, const GLchar *bytes);

  void CompileShaderRaw(GLuint id, const GLchar *source,
                        const std::string &headers = "\n",
                        const std::string &defines = glsl_version);

  GLuint CreateShader(ShaderType type);

private:
  bool _bind_loc;
  GLuint _id;
  std::vector<GLuint> _shaderId;
  // std::unordered_map<BuiltInUniform, > _builtInUniforms;

  static std::list<ProgramState *> _programs;
};

} // namespace Program

#endif